撰写正则表达式是一件很难的事情,因为我们不可能一次就写对。因此接下来要做的,就是总结一套顺畅的工作流程,允许通过迭代的方式逐步完成模式的设计工作。本节的巧妙之处在于如何从查找历史中回溯并编辑之前的记录。
在以下的示例文本中,单引号被当作引用标记。
search/quoted-strings.txt
This string contains a 'quoted' word.
This string contains 'two' quoted 'words.'
This 'string doesn't make things easy.'
要撰写一个正则表达式,用它匹配每一段被引号括起来的字符串。尽管这是一个需要多次尝试的过程,然而一旦匹配成功,便可以运行substitute命令将这些文本用真正的双引号括起来了,就像这样。
This string contains a “quoted” word.
This string contains “two” quoted “words.”
This “string doesn't make things easy.”
首先,进行一次粗略的查找。
➾ /\v'.+'
这个正则表达式会首先匹配一个 '
字符,然后匹配任意字符一次或多次,最终匹配另外一个 '
字符。执行完这条查找命令之后,我们的文档变成了下面这样。
This string contains a 'quoted' word.
This string contains 'two' quoted 'words.'
This 'string doesn't make things easy.'
第一行匹配正确,但第二行却出了问题。模式中的 .+
项执行了贪婪匹配,就是说它匹配了尽可能多的字符。但实际上,要在这行文本上得到两处独立的匹配,即每个括起来的单词都单独作为一处匹配。因此需要对之前的命令进行修正。
这一次,我们不用那个匹配任意字符的 . 符号,而是换成更具体的内容。实际上,我们要匹配的是除了 '
之外的任意字符,因此可以用 [^']+
。改进后的模式将变成以下模样。
➾ /\v'[^']+'
不必重新输入完整的命令,只需按 /<Up>
,查找域中便会出现上一次的模式。只需做一些小的改动,即用 <Left>
以及退格键将 . 字符从模式中删掉,然后输入新的内容。
执行查找时,将会得到以下匹配。
This string contains a 'quoted' word.
This string contains 'two' quoted 'words.'
This 'string doesn't make things easy.'
这一次有所进步。前两行匹配的内容正是我们想要的,但在第三行又出现新的问题。有一处被当作撇号使用的 ' 字符中断了匹配过程。因此,必须进一步改进模式。
现在,需要考虑撇号与闭引号的区别到底是什么。有不少这样的例子,如“won't”、“don't”以及 “we're”。每个示例中的 '
字符之后都紧跟着某个字母,而不是空格或者标点。因此,可以进一步修改模式。如果紧跟着 '
字符出现的是单词型字符,则前者也被当作普通字符处理。以下是新改进的版本。
➾ /\v'([^']|'\w)+'
这一次修改引入了一些相当有价值的内容。我们不仅额外增加了元字符 '\w
,而且将两种可选方案用括号括了起来并用竖线隔开。是时候让它大显神通了。
这一次,不是按 /<Up>
把上次的模式填到查找域,而是用 q/
调出命令行窗口。此窗口与一个常规的 Vim 缓冲区差不多,不过它的内容是查找历史,每行显示一条(参见结识命令行窗口)。这样就可以使用Vim强大的区分模式的编辑能力来修正上次的模式了。
以下编辑序列展示了如何完成这次特定的修改。如果你仍然没有弄懂 c%(<C-r>") <Esc>
的话,请参考技巧55以及技巧15。
一旦得到了理想模式,只需按 <CR>
键即可执行查找了。如下所示,文档中的匹配会被高亮起来。
This string contains a 'quoted'word.
This string contains 'two' quoted 'words.'
This 'string doesn't make things easy.'
太棒了!
我们的模式成功地完成了所有匹配,但在执行substitute命令之前,还需要进行最后一点修改,因为我们想捕获引号括起来的内容。以下是最终的模式。
➾ /\v'(([^']|'\w)+)'
既可以运行 /<Up>
并在查找域中编辑,也可以运行 q/
并在命令行窗口中修改。哪种方式用起来更顺手就用哪个。尽管这次查找的高亮结果与上次相比并无二致,但对于每一处匹配来说,引号所括的文本已经被赋给 \1
捕获寄存器了。这意味着可以运行以下substitute命令了。
➾ :%s//“\1”/g
将查找域留空,Vim将重用上一次的查找命令(更多细节,请跳至技巧91)。以下是运行命令的输出结果。
This string contains a “quoted” word.
This string contains “two” quoted “words.”
This “string doesn't make things easy.”